// 工厂模式是用来创建对象的一种最常用的设计模式，不暴露创建对象的具体逻辑，而是将将逻辑封装在一个函数中，那么这个函数就可以被视为一个工厂, 其就像工厂一样重复的产生类似的产品，工厂模式只需要我们传入正确的参数，就能生产类似的产品
// 对于使用者来讲, 不关心创建过程, 只关心创建结果以及后续的使用

/* 适用场景
1.如果你不想让某个子系统与较大的那个对象之间形成强耦合，而是想运行时从许多子系统中进行挑选的话，那么工厂模式是一个理想的选择
2.将new操作简单封装，遇到new的时候就应该考虑是否用工厂模式；
3.需要依赖具体环境创建不同实例，这些实例都有相同的行为,这时候我们可以使用工厂模式，简化实现的过程，同时也可以减少每种对象所需的代码量，有利于消除对象间的耦合，提供更大的灵活性 */

/* 例子
曾经我们熟悉的JQuery的$()就是一个工厂函数，它根据传入参数的不同创建元素或者去寻找上下文中的元素，创建成相应的jQuery对象 */

/*优点:
创建对象的过程可能很复杂，但我们只需要关心创建结果。
构造函数和创建者分离, 符合“开闭原则”
一个调用者想创建一个对象，只要知道其名称就可以了。
扩展性高，如果想增加一个产品，只要扩展一个工厂类就可以。
*/

/* 缺点
添加新产品时，需要编写新的具体产品类,一定程度上增加了系统的复杂度
考虑到系统的可扩展性，需要引入抽象层，在客户端代码中均使用抽象层进行定义，增加了系统的抽象性和理解难度 */

// 工厂模式根据抽象程度的不同可以分为：

// 简单工厂模式（Simple Factory）
// 工厂方法模式（Factory Method）
// 抽象工厂模式（Abstract Factory）

// 简单工厂方法:简单工厂模式也叫静态工厂模式，用一个工厂对象创建同一类对象类的实例

// function Factory(career) {
//   function User(career, work) {
//     this.career = career;
//     this.work = work;
//   }
//   let work;
//   switch (career) {
//     case "coder":
//       work = ["写代码", "修Bug"];
//       return new User(career, work);
//       break;
//     case "hr":
//       work = ["招聘", "员工信息管理"];
//       return new User(career, work);
//       break;
//     case "driver":
//       work = ["开车"];
//       return new User(career, work);
//       break;
//     case "boss":
//       work = ["喝茶", "开会", "审批文件"];
//       return new User(career, work);
//       break;
//   }
// }
// let coder = new Factory("coder");
// console.log(coder);
// let boss = new Factory("boss");
// console.log(boss);
// Factory就是一个简单工厂。当我们调用工厂函数时，只需要传递name、age、career就可以获取到包含用户工作内容的实例对象

// 工厂方法模式;
/* 工厂方法模式跟简单工厂模式差不多，但是把具体的产品放到了工厂函数的prototype中

这样一来，扩展产品种类就不必修改工厂函数了，核心类就变成抽象类，也可以随时重写某种具体的产品

也就是相当于工厂总部不生产产品了，交给下辖分工厂进行生产；但是进入工厂之前，需要有个判断来验证你要生产的东西是否是属于我们工厂所生产范围，如果是，就丢给下辖工厂来进行生产
 */
// 工厂方法
function Factory(career) {
  if (this instanceof Factory) {
    var a = new this[career]();
    return a;
  } else {
    return Factory(career);
  }
}

// 工厂方法函数的原型中设置所有对象的构造函数
Factory.prototype = {
  coder: function () {
    this.careerName = "程序员";
    this.work = ["写代码", "修Bug"];
  },
  hr: function () {
    this.careerName = "HR";
    this.work = ["招聘", "员工信息管理"];
  },
  driver: function () {
    this.careerName = "司机";
    this.work = ["开车"];
  },
  boss: function () {
    this.careerName = "老板";
    this.work = ["喝茶", "开会", "审批文件"];
  },
};
let coder =  Factory("coder");
console.log(coder);
let hr =  Factory("hr");
console.log(hr);

// 工厂方法关键核心代码是工厂里面的判断this是否属于工厂，也就是做了分支判断，这个工厂只做我能做的产品

// 抽象工厂模式
// 上述简单工厂模式和工厂方法模式都是直接生成实例，但是抽象工厂模式不同，抽象工厂模式并不直接生成实例， 而是用于对产品类簇的创建

// 通俗点来讲就是：简单工厂和工厂方法模式的工作是生产产品，那么抽象工厂模式的工作就是生产工厂的

// 由于JavaScript中并没有抽象类的概念，只能模拟，可以分成四部分：
// 用于创建抽象类的函数
// 抽象类
// 具体类
// 实例化具体类

let CareerAbstractFactory = function (subType, superType) {
  // 判断抽象工厂中是否有该抽象类
  if (typeof CareerAbstractFactory[superType] === "function") {
    // 缓存类
    function F() {}
    // 继承父类属性和方法
    F.prototype = new CareerAbstractFactory[superType]();
    // 将子类的constructor指向父类
    subType.constructor = subType;
    // 子类原型继承父类
    subType.prototype = new F();
  } else {
    throw new Error("抽象类不存在");
  }
};

/* 三、应用场景
从上面可看到，简单简单工厂的优点就是我们只要传递正确的参数，就能获得所需的对象，而不需要关心其创建的具体细节

应用场景也容易识别，有构造函数的地方，就应该考虑简单工厂，但是如果函数构建函数太多与复杂，会导致工厂函数变得复杂，所以不适合复杂的情况

抽象工厂模式一般用于严格要求以面向对象思想进行开发的超大型项目中，我们一般常规的开发的话一般就是简单工厂和工厂方法模式会用的比较多一些 */

// 综上，工厂模式适用场景如下：

// 1.如果你不想让某个子系统与较大的那个对象之间形成强耦合，而是想运行时从许多子系统中进行挑选的话，那么工厂模式是一个理想的选择
// 2.将new操作简单封装，遇到new的时候就应该考虑是否用工厂模式；
// 3.需要依赖具体环境创建不同实例，这些实例都有相同的行为,这时候我们可以使用工厂模式，简化实现的过程，同时也可以减少每种对象所需的代码量，有利于消除对象间的耦合，提供更大的灵活性
